# Copyright 2010-2015 Free Software Foundation, Inc.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# This file is part of the gdb testsuite.

# Test arm displaced stepping.

if {![is_aarch32_target]} then {
    verbose "Skipping arm displaced stepping tests."
    return
}

set testfile "arm-disp-step"
set srcfile ${testfile}.S
set binfile ${objdir}/${subdir}/${testfile}

set additional_flags "-Wa,-g"

if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable [list debug $additional_flags]] != "" } {
    untested arm-disp-step.exp
    return -1
}


#########################################
# Test ldm/stm related to PC.
proc test_ldm_stm_pc {} {
    global srcfile
    global gdb_prompt

    # Try to set breakpoint on test_ldm_stm_pc.  If symbol 'test_ldm_stm_pc'
    # can't be resolved, test case is compiled in Thumb mode, skip it.
    gdb_test_multiple "break *test_ldm_stm_pc" "break test_ldm_stm_pc" {
	-re "Breakpoint.*at.* file .*$srcfile, line.*\r\n$gdb_prompt $" {
	    pass "break test_ldm_stm_pc"
	}
	-re "No symbol.*\r\n$gdb_prompt $" {
	    pass "break test_ldm_stm_pc"
	    return 0
	}
    }

    gdb_test "break *test_ldm_pc" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_ldm_pc"
    gdb_test "break *test_ldm_stm_pc_ret" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_ldm_stm_pc_ret"

    gdb_continue_to_breakpoint "continue to test_ldm_stm_pc" \
	".*stmdb.*sp\!\,.*\{lr\, pc\}.*"
    gdb_continue_to_breakpoint "continue to test_ldm_pc" \
	".*ldmia.*sp\!\,.*\{pc\}.*"
    gdb_continue_to_breakpoint "continue to test_ldm_stm_pc_ret" \
	".*bx lr.*"
}

#########################################
# Test ldrX literal
proc test_ldr_literal {} {
    global srcfile
    global gdb_prompt

    gdb_test_multiple "break *test_ldr_literal" "break test_ldr_literal" {
       -re "Breakpoint.*at.* file .*$srcfile, line.*\r\n$gdb_prompt $" {
           pass "break test_ldr_literal"
       }
       -re "No symbol.*\r\n$gdb_prompt $" {
           return 0
       }
    }

    gdb_test "break *test_ldrsb_literal" \
       "Breakpoint.*at.* file .*$srcfile, line.*" \
       "break test_ldrsb_literal"
    gdb_test "break *test_ldrsh_literal" \
       "Breakpoint.*at.* file .*$srcfile, line.*" \
       "break test_ldrsh_literal"
    gdb_test "break *test_ldr_literal_end" \
       "Breakpoint.*at.* file .*$srcfile, line.*" \
       "break test_test_ldr_literal_end"

    gdb_continue_to_breakpoint "continue to test_ldr_literal" \
       ".*ldrh.*r0\,.*\[pc\].*"
    gdb_continue_to_breakpoint "continue to test_ldrsb_literal" \
       ".*ldrsb.*r0\,.*\[pc\].*"
    gdb_continue_to_breakpoint "continue to test_ldrsh_literal" \
       ".*ldrsh.*r0\,.*\[pc\].*"
    gdb_continue_to_breakpoint "continue to test_ldr_literal_ret" \
       ".*bx lr.*"
}

proc test_ldr_literal_16 {} {
    global srcfile
    global gdb_prompt

    gdb_test_multiple "break *test_ldr_literal_16" "break test_ldr_literal_16" {
	-re "Breakpoint.*at.* file .*$srcfile, line.*\r\n$gdb_prompt $" {
	    pass "break test_ldr_literal"
	}
	-re "No symbol.*\r\n$gdb_prompt $" {
	    return 0
	}
    }
    gdb_test "break *test_ldr_literal_16_end" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_ldr_literal_16_end"

    gdb_continue_to_breakpoint "continue to test_ldr_literal_16" \
	".*ldr.*r0\,.*L2.*"
    gdb_continue_to_breakpoint "continue to test_ldr_literal_16_end" \
	".*bx lr.*"
}

##########################################
# Test call/ret.
proc test_call_ret {} {
    global srcfile
    global testfile

    gdb_test "break *test_call" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_call"

    gdb_test "break *test_call_end" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_call_end"
    gdb_test "break *test_ret" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_ret"
    gdb_test "break *test_ret_end" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_ret_end"

    gdb_continue_to_breakpoint "test_call" ".*bl test_call_subr.*"
    gdb_continue_to_breakpoint "test_call_end" \
	".*@ Location test_call_end.*"
    gdb_continue_to_breakpoint "test_ret" \
	".*bx lr.*"
    gdb_continue_to_breakpoint "continue to test_ret_end" \
	".*@ Location test_ret_end.*"
}


#########################################
# Test branch
proc test_branch {} {
    global srcfile
    gdb_test "break *test_branch" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_branch"
    gdb_test "break *L_branch" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break Lbranch"

    gdb_continue_to_breakpoint "continue to test_branch" \
	".*b.*L_branch.*"
    gdb_continue_to_breakpoint "continue to Lbranch" \
	".*bx lr.*"
}

#########################################

# Test ldr from pc
proc test_ldr_from_pc {} {
    global srcfile
    gdb_test "break *test_ldr_pc" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_ldr_pc"
    gdb_test "break test_ldr_pc_ret" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_ldr_pc_ret"

    gdb_continue_to_breakpoint "continue to test_ldr_pc" \
	".*ldr.*r1\,.*\[pc, #0\].*"
    gdb_continue_to_breakpoint "continue to test_ldr_pc_ret" \
	".*bx lr.*"
}

#########################################

# Test cbz and cbnz
proc test_cbz_cbnz {} {
    global srcfile
    global gdb_prompt

    gdb_test_multiple "break *test_zero_cbnz" "break test_zero_cbnz" {
	-re "Breakpoint.*at.* file .*$srcfile, line.*\r\n$gdb_prompt $" {
	    pass "break test_ldr_literal"
	}
	-re "No symbol.*\r\n$gdb_prompt $" {
	    return 0
	}
    }

    gdb_test "break *test_zero_cbz" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_zero_cbz"
    gdb_test "break *test_non_zero_cbnz" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_non_zero_cbnz"
    gdb_test "break *test_non_zero_cbz" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_non_zero_cbz"

    gdb_continue_to_breakpoint "continue to test_zero_cbnz" \
	".*cbnz.*r0\,.*\.L3.*"
    gdb_continue_to_breakpoint "continue to test_zero_cbz" \
	".*cbz.*r0\,.*\.L3.*"
    gdb_continue_to_breakpoint "continue to test_non_zero_cbz" \
	".*cbz.*r0\,.*\.L4.*"
    gdb_continue_to_breakpoint "continue to test_non_zero_cbnz" \
	".*cbnz.*r0\,.*\.L4.*"
}

# Test adr

proc test_adr {} {
    global srcfile
    global gdb_prompt

    gdb_test_multiple "break *test_adr" "break test_adr" {
	-re "Breakpoint.*at.* file .*$srcfile, line.*\r\n$gdb_prompt $" {
	    pass "break test_adr"
	}
	-re "No symbol.*\r\n$gdb_prompt $" {
	    return 0
	}
    }

    gdb_test "break *test_adr_end" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_adr_end"

    gdb_continue_to_breakpoint "test_adr" \
	".*adr.*r0\,.*\.L8.*"
    gdb_continue_to_breakpoint "test_adr_end" \
	".*bx lr.*"
}

proc test_adr_32bit {} {
    global srcfile
    global gdb_prompt

    gdb_test_multiple "break *test_adr_32bit" "break test_adr_32bit" {
	-re "Breakpoint.*at.* file .*$srcfile, line.*\r\n$gdb_prompt $" {
	    pass "break test_adr"
	}
	-re "No symbol.*\r\n$gdb_prompt $" {
	    return 0
	}
    }

    gdb_test "break *test_adr_32bit_after" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_adr_32bit_after"

    gdb_test "break *test_adr_32bit_end" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_adr_32bit_end"

    gdb_continue_to_breakpoint "test_adr_32bit" \
	".*adr.*r0\,.*\.L6.*"
    gdb_continue_to_breakpoint "test_adr_32bit_after" \
	".*adr.*r0\,.*\.L6.*"
    gdb_continue_to_breakpoint "test_adr_32bit_end" \
	".*bx lr.*"
}

#########################################
# Test pop to PC
proc test_pop_pc {} {
    global srcfile
    gdb_test "break *test_pop_pc_1" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_1"
    gdb_test "break *test_pop_pc_2" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_2"
    gdb_test "break *test_pop_pc_3" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_3"

    gdb_test "break *test_pop_pc_ret" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_ret"

    gdb_test "break *test_pop_pc_1_right" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_1_right"
    gdb_test "break *test_pop_pc_1_wrong" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_1_wrong"
    gdb_test "break *test_pop_pc_2_right" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_2_right"
    gdb_test "break *test_pop_pc_2_wrong" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_2_wrong"
    gdb_test "break *test_pop_pc_3_right" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_3_right"
    gdb_test "break *test_pop_pc_3_wrong" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_pop_pc_1_wrong"

    gdb_continue_to_breakpoint "continue to test_pop_pc_1" \
	".*b.*\{r1\, pc\}.*"
    gdb_continue_to_breakpoint "continue to test_pop_pc_1_check" \
	".*b.*right.*"

    gdb_continue_to_breakpoint "continue to test_pop_pc_2" \
	".*\{pc\}.*"
    gdb_continue_to_breakpoint "continue to test_pop_pc_2_check" \
	".*b.*right.*"
    gdb_continue_to_breakpoint "continue to test_pop_pc_3" \
	".*\{r0\,r1\,r2\,r3\,r4\,r5\,r6\,r7\,pc\}.*"
    gdb_continue_to_breakpoint "continue to test_pop_pc_3_check" \
	".*b.*right.*"
    gdb_continue_to_breakpoint "continue to test_pop_pc_ret" \
	".*r7.*"
}

###########################################

proc test_str_pc {} {
    global srcfile
    global gdb_prompt

    gdb_test_multiple "break *test_str_pc" "break test_str_pc" {
	-re "Breakpoint.*at.* file .*$srcfile, line.*\r\n$gdb_prompt $" {
	    pass "break test_str_pc"
	}
	-re "No symbol.*\r\n$gdb_prompt $" {
	    pass "break test_str_pc"
	    return
	}
    }
    gdb_test "break *test_str_pc_end" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_str_pc_end"

    # Set breakpoint on both lables pc_offset_right and pc_offset_wrong
    gdb_test "break *pc_offset_right" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break pc_offset_right"
    gdb_test "break *pc_offset_wrong" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break pc_offset_wrong"

    gdb_continue_to_breakpoint "continue to test_str_pc" \
	".*str.*pc\,.*\[sp, #-4\].*"
    # If breakpoint on lable pc_offset_wrong is hit, that means the offset
    # computed in displaced stepping is different from offset computed
    # without displaced stepping.  Report a failure.
    gdb_continue_to_breakpoint "continue to pc_offset_right" \
	".*b.*test_str_pc_end.*"
    gdb_continue_to_breakpoint "continue to test_str_pc_end" \
	".*bx lr.*"
}

# Test 16 bit thumb instruction 'add rd, pc'.

proc test_add_rn_pc {} {
    global srcfile gdb_prompt

    set test "break test_add_rn_pc"
    gdb_test_multiple "break *test_add_rn_pc" $test {
	-re "Breakpoint.*at.* file .*$srcfile, line.*\r\n$gdb_prompt $" {
	    pass $test
	}
	-re "No symbol.*\r\n$gdb_prompt $" {
	    return
	}
    }

    gdb_continue_to_breakpoint "continue to test_add_rn_pc" \
	".*mov.*r3, 4.*"

    gdb_test "break *test_add_rn_pc_start" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_add_rn_pc_start"

    gdb_continue_to_breakpoint "continue to test_add_rn_pc_start" \
	".*add.*r3,.*pc.*"

    set pc_val [get_integer_valueof "\$pc" 0]

    gdb_test "break *test_add_rn_pc_end" \
	"Breakpoint.*at.* file .*$srcfile, line.*" \
	"break test_add_rn_pc_end"

    gdb_continue_to_breakpoint "continue to test_add_rn_pc_end" \
	".*bx lr.*"

    set r3_val [get_integer_valueof "\$r3" 0]
    # Test the value in r3 is correct.
    gdb_assert { [expr {$pc_val + 4 + 4} == $r3_val] }
}

# Get things started.

clean_restart ${testfile}

# Turn displaced stepping off before runto main. When displaced stepping
# is on, and we type 'run', GDB will first try to single step on _dl_debug_state,
# which is in library might be compiled in Thumb.
gdb_test_no_output "set displaced-stepping off"

if ![runto_main] then {
    fail "Can't run to main"
    return 0
}

gdb_test_no_output "set displaced-stepping on"
gdb_test "show displaced-stepping" ".* displaced stepping .* is on.*"

test_call_ret

test_branch

test_ldr_from_pc

test_ldm_stm_pc

test_ldr_literal

test_ldr_literal_16

test_cbz_cbnz

test_adr

test_adr_32bit

test_pop_pc

test_str_pc

test_add_rn_pc

##########################################

# Done, run program to exit.

gdb_continue_to_end "arm-disp-step"
